using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;

namespace GwtRpcNet.Rpc
{
    public class RpcNetReader
    {
        private string[] tokenList;
        private string[] stringList;
        private string[] numbList;
        private int i = 0;
        private ArrayList seenArray = new ArrayList();

        public RpcNetRequest DecodeRequest(string request)
        {
            i = 0;
            tokenList = request.Split(new char[] { '\uffff' });

            RpcNetRequest rpcRequest = new RpcNetRequest();

            //read version and flags
            rpcRequest.Version = Convert.ToInt32(tokenList[i++]);
            rpcRequest.Flags = Convert.ToInt32(tokenList[i++]);

            //create string and number list
            int callParams = Convert.ToInt32(tokenList[i++]);
            stringList = new string[callParams + 1];
            numbList = new string[tokenList.Length - i - callParams];
            Array.Copy(tokenList, i, stringList, 1, callParams);
            Array.Copy(tokenList, i + callParams, numbList, 0, numbList.Length);
            
            //start on first position of numbList
            i = 0;

            //read rpc request
            rpcRequest.ServiceUrl = ReadString();
            rpcRequest.StrongService = ReadString();
            rpcRequest.ServiceTypeName = ReadString();
            rpcRequest.ServiceMethodName = ReadString();

            //read parameter types
            int nMethPars = GetInt();
            rpcRequest.ServiceParamTypes = new Type[nMethPars];
            for (int j = 0; j < nMethPars; j++)
            {
                string type = ReadString();
                rpcRequest.ServiceParamTypes[j] = RpcNet.GetTypeForName(type);
            }

            //read parameter values
            rpcRequest.ServiceParamValues = new object[nMethPars];
            for (int j = 0; j < nMethPars; j++)
            {
                rpcRequest.ServiceParamValues[j] = ReadValue(rpcRequest.ServiceParamTypes[j]);
            }

            return rpcRequest;
        }

        #region Get

        private int GetInt()
        {
            return Convert.ToInt32(numbList[i++]);
        }

        #endregion

        #region Read

        public int ReadInt()
        {
            return (int)ReadValue(typeof(int));
        }

        public long ReadLong()
        {
            return (long)ReadValue(typeof(long));
        }

        private string ReadString()
        {
            return stringList[GetInt()];
        }

        private object ReadValue(Type valueType)
        {
            if (valueType == typeof(bool))
                return Convert.ToBoolean(numbList[i++]);
            else if (valueType == typeof(byte))
                return Convert.ToByte(numbList[i++]);
            else if (valueType == typeof(char))
                return Convert.ToChar(numbList[i++]);
            else if (valueType == typeof(double))
                return Convert.ToDouble(numbList[i++]);
            else if (valueType == typeof(float))
                return Convert.ToDouble(numbList[i++]);
            else if (valueType == typeof(int))
                return Convert.ToInt32(numbList[i++]);
            else if (valueType == typeof(long))
                return Convert.ToInt64(numbList[i++]);
            else if (valueType == typeof(short))
                return Convert.ToInt16(numbList[i++]);
            else if (valueType == typeof(string))
                return ReadString();

            return ReadObject();
        }

        public object ReadObject()
        {
            string type = ReadString();
            Type valueType = RpcNet.GetTypeForName(type);

            //use this to read Primitive types with no custom serializer
            if (RpcNet.IsPrimitiveType(valueType))
                return ReadValue(valueType);

            Type customSer = RpcNet.HasCustomFieldSerializer(valueType);
            object instance = Instantiate(customSer, valueType);
            RememberDecodedObject(instance);

            if (customSer != null)
                DeserializeCustom(customSer, valueType, instance);
            else
                DeserializeDefault(valueType, instance);

            return instance;
        }

        private object Instantiate(Type customSerializer, Type instanceType)
        {
            if (customSerializer != null)
            {
                try
                {
                    MethodInfo instantiate = customSerializer.GetMethod("Instantiate",
                        new Type[] { typeof(RpcNetReader) });
                    return instantiate.Invoke(null, new object[] { this });
                }
                catch (Exception)
                {
                    // purposely ignored
                }
            }

            if (instanceType.IsArray)
                throw new NotImplementedException();
            else
                return Activator.CreateInstance(instanceType);
        }

        private void RememberDecodedObject(object instance)
        {
            seenArray.Add(instance);
        }

        #endregion

        #region Deserialize

        private void DeserializeDefault(Type valueType, object result)
        {
            PropertyInfo[] props = valueType.GetProperties();
            Array.Sort(props, new PropertyInfoComparer());
            foreach (PropertyInfo prop in props)
            {
                object paramValue = ReadValue(prop.PropertyType);
                prop.SetValue(result, paramValue, null);
            }
        }

        private void DeserializeCustom(Type customSerializer, Type valueType, object result)
        {
            if (valueType.IsArray)
                throw new NotImplementedException();

            MethodInfo deserialize = customSerializer.GetMethod("DeSerialize", new Type[] {
                typeof(RpcNetReader), valueType});
            deserialize.Invoke(null, new Object[] { this, result });
        }

        #endregion
    }
}
